using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using LumenLiquid.Database;
using LumenLiquid.Database.Entities.Language;
using LumenLiquid.Database.Entities.Settings;
using LumenLiquid.Debug;
using LumenLiquid.InGame;
using LumenLiquid.Resources;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using NHibernate;
using DebugLevel = LumenLiquid.Debug.DebugHandler.EDebugLevel;

/***********************************
 *      CShark & VisibleBasix      *
 ***********************************/

namespace LumenLiquid
{
    /// <summary>
    /// This is the main class for our game
    /// </summary>
    public class Main : Game
    {

        #region WinAPI
        /// <summary>
        /// Checking whether this window is active or not
        /// </summary>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        private static extern IntPtr GetForegroundWindow();
        #endregion

        #region XNA
        private readonly GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;
        private bool _isWidescreen;
        #endregion

        #region Database
        private ISessionFactory _settingsDatabase;
        private ISessionFactory _translationDatabase;
        private Properties _properties;
        private IEnumerable<LanguageInfo> _languages;
        private ISession _translationSession;
        private readonly object _translationAccess = new object(); //Locking object for Thread safety

        public enum EProperties
        {
            Language = 1,
            GraphicsId = 2,
            Resolution = 3,
            StartupWorld = 4
        }

        #endregion

        #region Properties
        public GraphicsDevice Device { get; private set; }
        public static Main Instance { get; private set; }
        public Vector2 Screensize { get; private set; }
        public string Language { get; private set; }
        public Texturemanager Texturemanager { get; private set; }
        public Modelmanager Modelmanager { get; private set; }
        public Effectmanager Effectmanager { get; private set; }
        #endregion

        private MainGame _mainGame;

        public Main(IEnumerable<string> args)
        {
            DebugHandler.Log("Initializing main instance...", DebugLevel.Code);

            var appPath = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
            var wantedScreensize = Vector2.Zero;

            //Initialize content root
            Content.RootDirectory = Path.Content.ContentRoot;
            
            Instance = this;
            
            //Initialize ResourceHandler
            Texturemanager = new Texturemanager();
            Modelmanager = new Modelmanager();
            Effectmanager=new Effectmanager();

            #region Database

            try
            {
                _settingsDatabase = SessionFactory.CreateSettingsSessionFactory();
                _properties = new Properties(_settingsDatabase);
                _properties.LoadSettings();


                IStatelessSession session = _settingsDatabase.OpenStatelessSession();
                _languages = DatabaseInteraction.ReadAllObjects<LanguageInfo>(session);
                session.Close();


                Language =
                    _languages.First(m => m.Id == Convert.ToInt32(_properties[(int)EProperties.Language])).TableId;

                DebugHandler.Log("Set Language to \"" + Language + "\"");

                _translationDatabase = SessionFactory.CreateLanguageSessionFactory();
                _translationSession = _translationDatabase.OpenSession();


                var graphicHash = Convert.ToInt32(_properties[(int)EProperties.GraphicsId]); //To check whether the graphics card is the same and if modi is supported
                var resolution = Convert.ToInt32(_properties[(int)EProperties.Resolution]); //Only save index on list

                //Check whether the resolution index fits to this adapter
                if (graphicHash == GraphicsAdapter.DefaultAdapter.DeviceId)
                {
                    _properties[(int)EProperties.GraphicsId] = GraphicsAdapter.DefaultAdapter.DeviceId.ToString(CultureInfo.InvariantCulture);
                    resolution = 0;
                }

                if (GraphicsAdapter.DefaultAdapter.SupportedDisplayModes[SurfaceFormat.Color].Count() <= resolution)
                    resolution = 0;

                //Get screensize from enumerator
                wantedScreensize =
                    new Vector2(
                        GraphicsAdapter.DefaultAdapter.SupportedDisplayModes[SurfaceFormat.Color].ElementAt(resolution).
                            Width,
                        GraphicsAdapter.DefaultAdapter.SupportedDisplayModes[SurfaceFormat.Color].ElementAt(resolution).
                            Height);

                //5x4 is not widescreen with AR=1.6666 and 16:9 is with AR=1.7777
                _isWidescreen =
                    GraphicsAdapter.DefaultAdapter.SupportedDisplayModes[SurfaceFormat.Color].ElementAt(resolution).
                        AspectRatio > 1.7d;

                _properties[(int)EProperties.Resolution] = resolution.ToString(CultureInfo.InvariantCulture);
            }
            catch (Exception e)
            {
                DebugHandler.Log("Error with the settings database:\n" + e.Message, DebugLevel.Error);
                Process.GetCurrentProcess().Kill();
            }
            #endregion

            #region SetUp Display


            _graphics = new GraphicsDeviceManager(this)
                            {
                                PreferredBackBufferHeight = (int)wantedScreensize.Y,
                                PreferredBackBufferWidth = (int)wantedScreensize.X
                            }; //Initialize Graphics
            DebugHandler.Log("Screensize set to " + wantedScreensize.X + "x" + wantedScreensize.Y);


            //Turn off, that Update() blocks Draw() if it is too slowly
            IsFixedTimeStep = false;
            _graphics.SynchronizeWithVerticalRetrace = true;

            try
            {
                _graphics.IsFullScreen = !args.ToList().Contains("-window");
            }
            catch
            {
                _graphics.IsFullScreen = true;
            }

            DebugHandler.Log("Running as " + (_graphics.IsFullScreen ? "fullsreen" : "windowed") + " application");
            #endregion

            DebugHandler.Log("Main instance initialized sucessfully.", DebugLevel.Code);
        }

        #region XNA Stuff
        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well
        /// </summary>
        protected override void Initialize()
        {
            Screensize = new Vector2(_graphics.GraphicsDevice.Viewport.Width, _graphics.GraphicsDevice.Viewport.Height);   //Initialize Screensize with of GraphicsDevice

            Device = _graphics.GraphicsDevice;                                                                            //Load the device to the Global Datastore

            //Device.BlendState = BlendState.Opaque;                                                                       //no idea, what this line is actually
            //Device.DepthStencilState = DepthStencilState.Default;                                                        //doing, but it has a use...
            //Device.RasterizerState = RasterizerState.CullCounterClockwise;
            //Device.SamplerStates[0] = SamplerState.LinearWrap;

            base.Initialize();

            //Just for now also initialize MainGame
            _mainGame = new MainGame(_properties, _settingsDatabase);
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);   //Create new spritebatch for 2DTexture drawing
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            _properties.DisposeSettings(true);
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (Window.Handle != GetForegroundWindow()) return; //Exit update if not foreground window

            _mainGame.Update(gameTime);
#if DEBUG
            //In Debug enable Escape or Gamepad-Back for exiting game whenever needed (remove later?)

            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                Keyboard.GetState().IsKeyDown(Keys.Escape))
                EndGame();
#endif

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            #region Start
            GraphicsDevice.Clear(Color.Black);                          //First clear the GD with a black background

            _spriteBatch.Begin(SpriteSortMode.Immediate, null);   //Start painting 2DTextures to the screen
            #endregion

            _mainGame.Draw(gameTime);

            #region end
            _spriteBatch.End();                                       //Stop painting 2DTextures to the screen

            base.Draw(gameTime);
            #endregion

        }

        /// <summary> 
        /// Attempt to set the display mode to the desired resolution.  Itterates through the display 
        /// capabilities of the default graphics adapter to determine if the graphics adapter supports the 
        /// requested resolution.  If so, the resolution is set and the function returns true.  If not, 
        /// no change is made and the function returns false. 
        /// </summary> 
        /// <param name="width">Desired screen width.</param> 
        /// <param name="height">Desired screen height.</param> 
        /// <param name="fullScreen">True if you wish to go to Full Screen, false for Windowed Mode.</param> 
        public bool InitGraphicsMode(int width, int height, bool fullScreen)
        {
            // If we aren't using a full screen mode, the height and width of the window can 
            // be set to anything equal to or smaller than the actual screen size. 
            if (fullScreen == false)
            {

                _graphics.PreferredBackBufferWidth = width;
                _graphics.PreferredBackBufferHeight = height;
                _graphics.IsFullScreen = false;
                _graphics.ApplyChanges();
                Screensize = new Vector2(width, height);
                return true;

            }


            // If we are using full screen mode, we should check to make sure that the display 
            // adapter can handle the video mode we are trying to set.  To do this, we will 
            // iterate thorugh the display modes supported by the adapter and check them against 
            // the mode we want to set. 
            if (GraphicsAdapter.DefaultAdapter.SupportedDisplayModes.Any(dm => (dm.Width == width) && (dm.Height == height)))
            {
                _graphics.PreferredBackBufferWidth = width;
                _graphics.PreferredBackBufferHeight = height;
                _graphics.IsFullScreen = true;
                _graphics.ApplyChanges();
                Screensize = new Vector2(width, height);
                return true;
            }
            //The mode is not supported, return
            return false;

        }
        #endregion

        public List<SIdInfo> Languages
        {
            get
            {
                return _languages.Select(info => new SIdInfo(info.Id, info.LocalizedName)).ToList();
            }
        }

        public bool SwitchLanguage(int id)
        {
            if (_languages.Select(m => m.Id.Equals(id)).Count() != 0)
            {
                lock (_translationAccess)
                {
                    _translationSession.Close();
                    _translationDatabase.Close();

                    Language = _languages.Where(m => m.Id.Equals(id)).ElementAt(0).TableId;

                    _translationDatabase = SessionFactory.CreateLanguageSessionFactory();
                    _translationSession = _translationDatabase.OpenSession();
                }

                DebugHandler.Log("Switched Language to \"" + Language + "\"");
                return true;
            }

            return false;
        }

        public string Translation(string id)
        {
            lock (_translationAccess)
            {
                var translation = DatabaseInteraction.ReadObject<Translation>(_translationSession, id);
                if (translation != null)
                    return translation.Translated;
            }

            DebugHandler.Log("Tried to access missing translation id \"" + id + "\" in language \"" + Language + "\"", DebugLevel.Warning);
            return "";
        }

        public void EndGame()
        {
            Exit();    //only for call from outside - like "exit" in mainmenu or console
        }
    }
}
